'''
Created on 9/04/2012

@author: Stefan Schliebs (sschliebs@gmail.com)
'''


import os
import subprocess
import glob

class ConvertToVideo ():
    """
    Exports the slide images and the audio files into a movie file.
    
    For this to work the following dependencies have to be met: 
    - ffmpeg (for converting images and mp3 files into movies)
    - mencoder (for fixing final movie after concatenation of avi files)
    - awk (for processing the textual output of mencoder; awk should be 
      available on every linux box)
    - generated image and audio files using AudioConverter and ODPConverter
        
    The overall process is as follows:
    
    0. We assume images and audio files have been generated.
    1. We need to determine the length of each mp3 file using
       ffmpeg -i audio/Slide1.mp3 2>&1 | awk '/Duration/ { print substr($2,0,length($2)-1) }'
       
       (example detects length of file audio/Slide1.mp3)
    2. We use mencoder to create a movie for each slide consisting of a
       single image and the corresponding audio file. The length of this
       movie corresponds to the length of the audio file which we have
       determined in the previous step.
       
       mencoder mf://images/Slide1.png -o out1.avi -ovc lavc -lavcopts vcodec=mpeg4 \
          -audiofile audio/Slide1.mp3 -oac copy -fps 1/XXX -ofps 30
          
       Replace XXX with the time (in seconds) of the audio file 'audio/Slide1.mp3'.
       For example, '-fps 1/4.1' (note that fractions of a second are possible.
    3. Each of the created slide movies can be concatenated into a single movie file
       using simple concatenation of files:
       
       cat out1.avi out2.avi > out.avi
    4. Finally, we need to re-encode the concatenated file. (Reasons are not 
       quite clear to me, but it obviously fixes some playback errors with
       diverse video players.
       
       mencoder out.avi -o final.avi -forceidx -ovc copy -oac copy
       
       'final.avi' represents the final movie.
    
    Some sseful links describing the above procedure:
    http://askubuntu.com/questions/18468/how-to-add-music-to-an-image-with-mencoder
    http://www.munz.li/2008/07/23/time-lapse-convert-single-jpg-images-to-a-movie
    http://superuser.com/questions/63399/get-mp3-length-in-linux-freebsd
    """
    
    def __init__(self, img_files, audio_files, tmp_dir, output_file):
        """
        Exports the slide images and the audio files into a movie file.
    
        @param img_files: list of string indicating the location of the 
            image files
        @param audio_files: list of string indicating the location of the 
            audio files
        @param tmp_dir: temporary directory for exporting temporary video 
            files 
        @param output_file: path to the target output file
        """

        # copy parameter data
        self.img_files = img_files
        self.audio_files = audio_files
        self.output_file = output_file
        self.output_dir = tmp_dir
        
        # start conversion
        self.convert()

    def detect_mp3_length(self, mp3_file):
        """
        Detect length of mp3 file.
        
        @param mp3_file: audio file for which length is determined
        @return: time string in format SS.FRAMES 
        """
        # shell command to detect length
        CMD_MP3_LENGTH = "ffmpeg -i %s 2>&1 | awk '/Duration/ { print substr($2,0,length($2)-1) }'"

        # now start a subprocess to execute the command        
#        time_str = subprocess.check_output([CMD_MP3_LENGTH % mp3_file])
        p = os.popen(CMD_MP3_LENGTH % mp3_file)
        time_str = p.readline().strip('\n')
        p.close()
        return self._get_seconds(time_str)
    
    def _get_seconds(self, time_str):
        """
        Converts a time string of the format HH:MM:SS.FRAME into
        a string of only seconds (SS.FRAMES).
        
        @param time_str: input time string in format HH:MM:SS.FRAME
        @return: time string in format SS.FRAMES
        """
        t = time_str.split(':')
        seconds, frames = t[-1].split('.')
        result = str(int(t[0]) * 3600 + int(t[1]) * 60 + int(seconds)) + "." + str (frames)
        return result

    
    def export_slide(self, img_file, audio_file, output_file, audio_length):
        """
        Export the given image and audio file into a single output movie.
        
        @param img_file: Path to the slide image
        @param audio_file: Path to the audio file
        @param output_file: Path to the output file for the movie
        @param audio_length: Length of audio_file in the format 'SECONDS.FRAMES'
        
        @return: None   
        """
        
        # mencoder command
        CMD_EXPORT_SLIDE = "mencoder mf://%s -o %s -ovc lavc -lavcopts " \
            + "vcodec=mpeg4 -audiofile %s -oac copy -fps 1/%s -ofps 30"
        
        # now start a subprocess to execute the command        
#        subprocess.call([CMD_EXPORT_SLIDE % (img_file, output_file, 
#                                             audio_file, audio_length)])
        os.system(CMD_EXPORT_SLIDE % (img_file, output_file, 
                                      audio_file, audio_length))
        
    def concatenate_movies(self, movie_list, output_file):
        """
        Concatenate the movies in the given list and store the result
        into output_file.
        
        @param movie_list: list of paths to slide movies
        @param output_file: path to the output file
        @return: None
        """
        # we need to produce something like this:
        # cat out1.avi out2.avi > out.avi
        cmd = "cat " + " ".join(movie_list) + " > " + output_file
#        subprocess.call([cmd])
        os.system(cmd)

    def finalize_movie(self, input_file, output_file):
        """
        Re-encode the concatenated movies for video player
        compatibility reasons.
        
        @param input_file: location of the input file
        @param output: location of the input file
        """
        # we need to produce something like this:
        # mencoder input.avi -o output.avi -forceidx -ovc copy -oac copy
        CMD_FINALIZE = "mencoder %s -o %s -forceidx -ovc copy -oac copy"
#        subprocess.call([CMD_FINALIZE % (input_file, output_file)])
        os.system(CMD_FINALIZE % (input_file, output_file))
        
    def convert(self):
        """
        Converts the slides and audio files into a movie clip.
        """
        # first some sanity checks
        if not len(self.audio_files) == len(self.img_files):
            raise Exception("Length of image and audio list do not match!")
        
        l_output = []
        for i, slide in enumerate(zip(self.img_files, self.audio_files)):
            # unpack tuple
            img, audio = slide
            
            # detect length of the mp3 file
            audio_length = self.detect_mp3_length(audio)
            
            # export slide to movie
            output = os.path.join(self.output_dir,"out_%d.avi" % i)
            l_output.append(output)
            self.export_slide(img, audio, output, audio_length)
            
        # concatenate output files
        concatenated = os.path.join(self.output_dir,"concatenated.avi")
        self.concatenate_movies(l_output, concatenated)
        
        # finalise movie
        self.finalize_movie(concatenated, self.output_file)
        

